#include <array>
#include <ctime>
#include <random>

#include "ArrayTests.h"
#include "../TestFramework/TestArray.h"

namespace
{
    template <class T>
    class TestProcessor : public TestFramework::ArrayProcessor<T>
    {
    public:
        virtual void ProcessItem(T& item)
        {
            item += item;
        }
    };

    template <class T>
    class TestComparer : public TestFramework::ArrayComparer<T>
    {
    public:
        virtual bool Compare(const T& lhs, const T& rhs) const
        {
            return lhs > rhs;
        }
    };

    struct TestObject
    {
        TestObject()
            : A(99)
            , B(1)
        {}

        int A;
        double B;
    };

    TEST_CASE(TestArrayCreation)
    {
        TestFramework::TestArray<int, 10> mass1;
        CHECK(mass1.GetSize() == 10);
    }

    TEST_CASE(TestArrayFilling)
    {
        TestFramework::TestArray<int, 100> mass1;
        mass1.Fill(99);
        TestFramework::TestArray<int, 100>::ConstIterator iter = mass1.Begin();
        for (; iter < mass1.End(); ++iter)
        {
            CHECK(*iter == 99);
        }
    }

    TEST_CASE(TestArrayInitialization)
    {
        TestFramework::TestArray<int, 50> mass1;
        TestFramework::TestArray<int, 50>::Iterator iter = mass1.Begin();
        for (int i = 0; iter < mass1.End(); ++iter, ++i)
        {
          *iter = i;
        }

        for (int i = 0; i < 50; ++i)
        {
          CHECK(mass1[i] == i);
        }
    }

    TEST_CASE(TestArrayProcessor)
    {
        typedef TestFramework::TestArray<int, 50> MassType;
        MassType mass1;
        for (int i = 0; i < 50; ++i)
        {
          mass1[i] = i;
        }

        TestProcessor<MassType::ValueType> processor;
        mass1.Enumerate(processor);
        for (int i = 0; i < 50; ++i)
        {
          CHECK(mass1[i] == (i + i));
        }
    }

    TEST_CASE(TestArraySorting)
    {
        typedef TestFramework::TestArray<int, 1000> MassType;
        MassType mass;
        mass.Fill(0);
        std::srand(unsigned(std::time(0)));
        for (int i = 0; i < 1000; ++i)
        {
            mass[i] = std::rand() % 9;
        }

        TestComparer<MassType::ValueType> comparer;
        mass.Sort(comparer);
        for (int i = 0; i < 999; ++i)
        {
            CHECK(mass[i] <= mass[i + 1]);
        }
    }

    TEST_CASE(TestArrayIteratorsCheck)
    {
        typedef TestFramework::TestArray<int, 50> MassType;
        MassType mass1;
        MassType::Iterator iter = mass1.Begin();
        int i = 0;
        for (; iter != mass1.End(); ++iter, ++i) {}
        CHECK(i == 50);
        for (; iter != mass1.Begin(); --iter, --i) {}
        CHECK(i == 0);
    }

    TEST_CASE(TestArrayCheckOutOfRange)
    {
        TestFramework::TestArray<int, 20> mass;
        CHECK_EXCEPTION_OCCURRENCE(mass[100] = 10);
    }

    TEST_CASE(TestArrayWithObject)
    {
        TestFramework::TestArray<TestObject, 1000> objects;
        CHECK(objects[555].A == 99 && objects[555].B == 1);
        objects[555].A = 0;
        objects[555].B = 0;
        CHECK(objects[555].A == 0 && objects[555].B == 0);
        objects.Clear();
        CHECK(objects[555].A == 99 && objects[555].B == 1);
    }

    PERFORMANCE_TEST_CASE(TestArrayPerformance)
    {
        START_ETALON
            std::array<TestObject, 50000> etalon;
            for (std::array<TestObject, 50000>::iterator iter = etalon.begin(); iter < etalon.end(); ++iter)
            {
                iter->A = iter->A * iter->A;
                iter->B = iter->B * iter->B;
            }

            etalon.fill(TestObject());
        FINISH_ETALON

        START_TEST
            TestFramework::TestArray<TestObject, 50000> test;
            for (TestFramework::TestArray<TestObject, 50000>::Iterator iter = test.Begin(); iter < test.End(); ++iter)
            {
                iter->A = iter->A * iter->A;
                iter->B = iter->B * iter->B;
            }

            test.Fill(TestObject());
        FINISH_TEST

        CHECK_GREATER_PERFORMANCE
    }

    START_TEST_SUITE(ArrayTest, "Array test")
    {
        REGISTER_TEST(TestArrayCreation);
        REGISTER_TEST(TestArrayFilling);
        REGISTER_TEST(TestArrayInitialization);
        REGISTER_TEST(TestArrayIteratorsCheck);
        REGISTER_TEST(TestArrayProcessor);
        REGISTER_TEST(TestArraySorting);
        REGISTER_TEST(TestArrayCheckOutOfRange);
        REGISTER_TEST(TestArrayWithObject);
        REGISTER_TEST(TestArrayPerformance);
    }
    FINISH_TEST_SUITE
}

namespace Tests
{
    TestFramework::UnitTestPtr CreateArrayTester()
    {
        CREATE_SUITE(ArrayTest)
    }
}
